package puntaelena.facilitylocation.hillclimbing.solver.agents.coalitionspool;

import puntaelena.facilitylocation.hillclimbing.solver.agents.FacilityCoalition;
import puntaelena.facilitylocation.hillclimbing.solver.agents.FcltCityNegotiateComputator;
import static puntaelena.facilitylocation.hillclimbing.solver.agents.FcltCityNegotiateComputator.*;
import puntaelena.facilitylocation.hillclimbing.solver.services.IFcltLocDataService;
import puntaelena.facilitylocation.hillclimbing.solver.services.IHillClimbingAgentRegistry;
import puntaelena.facilitylocation.hillclimbing.solver.services.ICoalitionsListener;
import puntaelena.graphsupport.FacilitiesGraph;

import java.util.*;

import static laboratoryQ.computation.BaseUtils.*;

public class CoalitionPairsPool {
	private double eps = 1e-5;

	private TreeSet<CoalitionsPair> sortedPairs =
		new TreeSet<CoalitionsPair>();
	private Map<FacilityCoalition, List<CoalitionsPair>> coalition2pairsList =
		new HashMap<FacilityCoalition, List<CoalitionsPair>>();
	private List<FacilityCoalition> invalidatedList =
		new ArrayList<FacilityCoalition>();
	private int pairsMaxId = 0;

	private CoalitionsPair infimumPair = null;

	/**
	 * internal
	 */
	private int treeVersion = FcltCityNegotiateComputator.NOT_INIT_VERSION;
	/**
	 * external
	 */
	private int coalitionPairsVersion = FcltCityNegotiateComputator.INIT_VERSION;

	public void init(FacilitiesGraph fgraph) {
		infimumPair = CoalitionsPair.infimum(fgraph);
	}

	public CoalitionsPair searchBestPair(
		IFcltLocDataService flServ,
		IHillClimbingAgentRegistry hcagReg,
		ICoalitionsListener cltnsListen
	) {
		if (infimumPair == null) {
			throw new RuntimeException("Call init(...) first");
		}
		updateTree(flServ, hcagReg, cltnsListen);
		updateInvalidated(flServ, hcagReg);
		assert _ac_alivePairsHaveActualSim(hcagReg, flServ);
		assert _ac_alivePairsAreUptodate(flServ);

		CoalitionsPair topSorted = extractBestPair(flServ);
		assert _ac_pairIsBest(topSorted, hcagReg, flServ);
		return topSorted;
	}

	public void invalidateCoalition(FacilityCoalition coalition) {
		invalidatedList.add(coalition);
	}

	public void increaseCoalitionPairsVersion() {
		coalitionPairsVersion++;
	}

	public void onCoalitionDisappear(FacilityCoalition coalition) {
		reassert(coalition2pairsList.remove(coalition) != null);
	}

	private void updateTree(
		IFcltLocDataService flServ,
		IHillClimbingAgentRegistry hcagReg,
		ICoalitionsListener cltnsListen
	) {
		if (treeVersion < coalitionPairsVersion) {
			rebuildSortedPairsTree(
				flServ,
				hcagReg
			);
			cltnsListen.onTreeRebuild();
			treeVersion = coalitionPairsVersion;
			invalidatedList.clear();
		} else {
			cltnsListen.onTreeSoftUpdate();
		}
	}

	private void updateInvalidated(
		IFcltLocDataService flServ,
		IHillClimbingAgentRegistry hcagReg
	) {
		assert new HashSet<FacilityCoalition>(invalidatedList).size() == invalidatedList.size();
		for (FacilityCoalition invalidCoalition : invalidatedList) {
			List<CoalitionsPair> pairs =
				coalition2pairsList.get(invalidCoalition);
			assert pairs != null;
			for (
				Iterator<CoalitionsPair> iter = pairs.iterator();
				iter.hasNext();
			) {
				CoalitionsPair pair = iter.next();
				if (!pair.isAlive(flServ)) {
					iter.remove();
					continue;
				}
				updatePair(pair, hcagReg, sortedPairs);
			}
		}
		invalidatedList.clear();
	}

	private static void updatePair(
		CoalitionsPair pair,
		IHillClimbingAgentRegistry hcagReg,
		TreeSet<CoalitionsPair> sortedPairs
	) {
		reassert(sortedPairs.remove(pair));
		pair.update(hcagReg);
		sortedPairs.add(pair);
	}

	private static void rebuildSortedPairsTree(
		List<FacilityCoalition> coalitions,
		IHillClimbingAgentRegistry hcagReg,
		TreeSet<CoalitionsPair> sortedPairs,
		Map<FacilityCoalition, List<CoalitionsPair>>
			coalition2pairsList,
		CoalitionPairsPool pairId_ptr
	) {
		sortedPairs.clear();
		coalition2pairsList.clear();

		int n = coalitions.size();
		for (int i = 0; i < n; i++) {
			FacilityCoalition coalitionA = coalitions.get(i);
			for (int j = i + 1; j < n; j++) {
				FacilityCoalition coalitionB = coalitions.get(j);
				assert coalitionA != coalitionB;
				CoalitionsPair cpair = new CoalitionsPair(
					coalitionA,
					coalitionB,
					pairId_ptr.pairsMaxId++,
					hcagReg
				);
				registerPair(cpair, coalition2pairsList);
				sortedPairs.add(cpair);
			}
		}
	}

	private void rebuildSortedPairsTree(
		IFcltLocDataService flServ,
		IHillClimbingAgentRegistry hcagReg
	) {
		rebuildSortedPairsTree(
			getAllCoalitions(hcagReg, flServ),
			hcagReg,
			sortedPairs,
			coalition2pairsList,
			this
		);
	}

	private static TreeSet<CoalitionsPair> buildSortedPairsTree(
		List<FacilityCoalition> coalitions,
		IHillClimbingAgentRegistry hcagReg,
		CoalitionPairsPool pairId_ptr
	) {
		TreeSet<CoalitionsPair> sortedPairs =
			new TreeSet<CoalitionsPair>();
		rebuildSortedPairsTree(
			coalitions,
			hcagReg,
			sortedPairs,
			new HashMap<FacilityCoalition, List<CoalitionsPair>>(),
			pairId_ptr
		);
		return sortedPairs;
	}

	private CoalitionsPair extractBestPair(IFcltLocDataService flServ) {
		return extractBestPair(true, sortedPairs, flServ);
	}

	private CoalitionsPair extractBestPair(
		boolean removeDead,
		TreeSet<CoalitionsPair> sortedPairs,
		IFcltLocDataService flServ
	) {
		for (Iterator<CoalitionsPair> iter = sortedPairs.iterator(); iter.hasNext();) {
			CoalitionsPair result = iter.next();
			if (result.isAlive(flServ)) {
				assert result.isUptodate();
				if (removeDead) {
					sortedPairs.remove(result);
				}
				return result;
			} else {
				iter.remove();
			}
		}
		assert sortedPairs.isEmpty();
		return infimumPair;
	}

	private static void registerPair(
		CoalitionsPair pair,
		Map<FacilityCoalition, List<CoalitionsPair>> coalition2pairsList
	) {
		getCoalitionPairsList(pair.coalA, coalition2pairsList).add(pair);
		getCoalitionPairsList(pair.coalB, coalition2pairsList).add(pair);
	}

	private static List<CoalitionsPair> getCoalitionPairsList(
		FacilityCoalition coalition,
		Map<FacilityCoalition, List<CoalitionsPair>> coalition2pairsList
	) {
		List<CoalitionsPair> list = coalition2pairsList.get(coalition);
		if (list == null) {
			list = new ArrayList<CoalitionsPair>();
			coalition2pairsList.put(coalition, list);
		}
		return list;
	}

	// assertion checks

	private boolean _ac_alivePairsHaveActualSim(IHillClimbingAgentRegistry hcagReg, IFcltLocDataService flServ) {
		for (CoalitionsPair pair : sortedPairs) {
			if (!pair.isAlive(flServ)) {
				continue;
			}
			double similaryty = computeRateSimilarity(pair.coalA, pair.coalB, hcagReg);
			assert Math.abs(similaryty - pair.simMsr) < eps;
			if (Math.abs(similaryty - pair.simMsr) >= eps) {
				return false;
			}
		}
		return true;
	}

	private boolean _ac_alivePairsAreUptodate(IFcltLocDataService flServ) {
		for (CoalitionsPair pair : sortedPairs) {
			if (!pair.isAlive(flServ)) {
				continue;
			}
			assert pair.isUptodate();
			if (!pair.isUptodate()) {
				return false;
			}
		}
		return true;
	}

	private boolean _ac_pairIsBest(
		CoalitionsPair topSorted,
		IHillClimbingAgentRegistry hcagReg,
		IFcltLocDataService flServ
	) {
		TreeSet<CoalitionsPair> sortedPairsA = buildSortedPairsTree(
			getAllCoalitions(hcagReg, flServ),
			hcagReg,
			this
		);
		CoalitionsPair bestPair = extractBestPair(true, sortedPairsA, flServ);
		assert pairsEqual(bestPair, topSorted, eps);
		assert topSorted == infimumPair || topSorted.isAlive(flServ);
		return pairsEqual(bestPair, topSorted, eps);
	}
}
